// 本文件对Block类的方法进行详细定义
// @Author: zhengqi Wu
// @Description:
// @Version: 1.0.0
// @Date: 2021/2/27 9:11 下午
// @Copyright: MIN-Group；国家重大科技基础设施——未来网络北大实验室；深圳市信息论与未来网络重点实验室
//
const TLV = require('./TLV')

const encoder = require('./Encoder').Encoder
/**
 * 传入TLV 构造一个 TLV Block
 * @param tlvType {Number}
 * @param tlvLength {Number}
 * @param buffer {ArrayBuffer}
 * @param verifyLength {boolean}
 * @returns {Block} block
 */
function createBlockByTypeLengthBuffer(tlvType, tlvLength, buffer, verifyLength){
    //DONE 错误处理
    let block = new Block()
    try{
        block.buildByTypeLengthBuffer(tlvType, tlvLength, buffer, verifyLength)
        return block
    }catch (e){
        throw e
    }
}

/**
 * 从一个包含TLV的字节数组中构造一个 Block 对象
 * @param buffer {ArrayBuffer}
 * @param verifyLength bool 是否要验证TLV的长度是否匹配，如果true，则会对比解析到的 TLV-LENGTH 与 TLV-VALUE的实际长度是否匹配
 * @returns {Block}
 */
function createBlockByBuffer(buffer, verifyLength){
    if(buffer.byteLength === 0){
        //DONE 错误处理
        throw new BlockError("Empty buffer")
    }
    try{
        let tlvType = TLV.readType(buffer, (0))
        let tlvLength = TLV.readVarNumber(buffer, (TLV.sizeOfVarNumber(tlvType)))
        return createBlockByTypeLengthBuffer(tlvType, tlvLength, buffer.slice((TLV.sizeOfVarNumber(tlvLength) + TLV.sizeOfVarNumber(tlvType)), buffer.byteLength), verifyLength)
    }catch (e){
        throw e
    }

}

class Block {
    constructor() {
        this.tlvType = "" // TLV-TYPE
        this.length = "" // TLV-LENGTH
        this.value = new ArrayBuffer(0) // TLV-VALUE
        this.elements = [] // TLV-sub-elements
        this.raw = new ArrayBuffer(0) // TLV编码后的字节数组
    }

    /**
     * 通过另一个Block来初始化当前Block
     * @param block {Block}
     */
    buildByBlock(block){
        // 错误处理
        try{
            this.buildByTypeLengthBuffer(block.getType(), block.getLength(), block.getValue(), true)    
        }catch(e){
            throw e
        }
    }

    /**
     * 通过 T、L、V 来初始化一个Block
     * @param tlvType {Number}
     * @param tlvLength {Number}
     * @param buffer {ArrayBuffer}
     * @param verifyLength {boolean}
     */
    buildByTypeLengthBuffer(tlvType, tlvLength, buffer, verifyLength){
        this.tlvType = tlvType
        this.length = tlvLength
        // console.log("this.length",verifyLength &&this.length,"buffer.byteLength",buffer.byteLength)
        if(verifyLength && this.length !== buffer.byteLength){
            // DONE 错误处理
            throw new BlockError("TLV-LENGTH does not match buffer size!")
        }

        let enc = new encoder()
        enc.encoderReset(BigInt(TLV.sizeOfVarNumber(tlvType) + TLV.sizeOfVarNumber(tlvLength) + buffer.byteLength),0n)
        enc.prependByteArray(buffer, BigInt(buffer.byteLength))
        enc.prependVarNumber(tlvLength)
        enc.prependVarNumber(tlvType)
        let encBuffer = enc.getBuffer()
        this.raw = encBuffer
        this.value = encBuffer.slice(TLV.sizeOfVarNumber(tlvType) + TLV.sizeOfVarNumber(tlvLength))
    }
    /**
     * Get TLV-type
     * @returns {Number}
     */
    getType () {
        return this.tlvType
        // test git
    }

    /**
     * Set tlvType
     * @param tlvType {BigInt | Number}
     */
    setType (tlvType) {
        this.tlvType = tlvType
    }

    /**
     * Get TLV-Length
     * @returns {Number | BigInt}
     */
    getLength () {
        return this.length
    }

    /**
     * Set TLV-Length
     * @param length {BigInt | Number}
     */
    setLength (length) {
        this.length = length
    }
    //
    // Get TLV-VALUE
    //
    // @Description:
    // @return []byte
    //
    getValue () {
        return this.value
    }

    /**
     * Set TLV-VALUE
     * @param value {ArrayBuffer}
     */
    setValue (value) {
        this.value = value
    }
    //
    // 获取 TLV 编码好的字节数组
    //
    // @Description:
    // @receiver b
    // @return {ArrayBuffer}
    //
    getRaw () {
        return this.raw
    }

    /**
     * 判断当前的TLV Block是否有效
     * @returns {boolean}
     */
    isValid(){
        return this.tlvType !== TLV.PacketType.TlvInvalid
    }

    /**
     * 整个TLV编码后的大小，包含T和L
     * @returns {Number}
     */
    size(){
        if(!this.isValid()){
            // DONE 错误处理
            throw new BlockError("Can not cal size of invalid block!")
        }
        return TLV.sizeOfVarNumber(this.tlvType) + TLV.sizeOfVarNumber(this.length) + this.value.byteLength;
    }

    /**
     * 判断Block对象是否含有value
     * @returns {boolean}
     */
    hasValue(){
        if(this.value == undefined){
            return false//value未初始化时，不能直接取bytelength
        }else{
            return this.value.byteLength !== 0
        }
    }

    /**
     * 判断Block对象是否包含Raw数组
     * 1. 如果包含Raw数组，说明当前的Block已经被Encode过一次了，不能再修改
     * 2. 每个Block只能被encode一次，如果encode一次之后再修改，则再次调用encode的时候会什么也不做
     * @returns {boolean}
     */
    hasRaw(){
        return this.raw.byteLength !== 0 && undefined
    }

    /**
     * 判断当前Block是否包含子TLV
     * @returns {boolean}
     */
    hasSubElement(){
        return this.elements.length !== 0
    }

    /**
     * 获取所有的一级子TLV Block
     * 每次通过CreateBlockBy*系列工厂方法构造出Block之后，需要手动调用ParseSubElements方法，才会去解析一级子TLV
     * @returns {string}
     */
    getSubElements(){
        return this.elements
    }

    /**
     * 获取所有的一级子TLV Block
     */
    parseSubElements(){
        if(this.value.byteLength === 0 || this.elements.length !== 0){
            return
        }
        try{
            for(let start = 0; start < this.value.byteLength; ){
                // DONE 错误处理（TLV） 清空this.elements
                // 读取TLV-Type
                let tlvType = 0

                tlvType = TLV.readType(this.value, (start))
                start += TLV.sizeOfVarNumber(tlvType)

                // 读取TLV-Length
                let tlvLength = 0

                tlvLength = TLV.readVarNumber(this.value, (start))

                start += TLV.sizeOfVarNumber(tlvLength)

                // 检查 buffer中是否有足够的字节
                if(tlvLength + start > this.value.byteLength){
                    // DONE 错误处理
                    this.clearElements()
                    throw new BlockError(`TLV-LENGTH of sub-elememt of type %d "+
                "exceeds TLV-VALUE boundary of parent block {tlvType}`)
                }

                // DONE 创建一个子Block 并添加到elements当中

                let block = createBlockByTypeLengthBuffer(tlvType, tlvLength, this.value.slice(start, start + tlvLength), true)
                // DONE 错误处理 清空this.elements
                this.elements.push(block)
                start += tlvLength
            }
        }catch (e) {
            this.clearElements()
            throw e
        }

    }

    /**
     * 从elements中找到第一个指定type的子TLV
     * @param tlvType
     * @returns {string|null}
     */
    getElement(tlvType){
        for(let i = 0; i < this.elements.length;i++){
            if(this.elements[i].tlvType === tlvType){
                return this.elements[i]
            }
        }
        return null
    }

    /**
     * 移除所有指定type的TLV
     * @param tlvType
     */
    removeElement(tlvType){
        let res = []
        for(let i = 0; i < this.elements.length; i++){
            if(this.elements[i].tlvType !== tlvType){
                res.push(this.elements[i]);
            }
        }
        this.elements = res
    }

    /**
     * 往elements中添加一个子TLV
     * @param block
     */
    addElement(block){
        this.elements.push(block)
    }

    /**
     * 清除elements中的所有子TLV
     */
    clearElements(){
       this.elements.length = 0
    }

    encode(enc){
        // 如果已经有raw，则说明编码过，或者是解码而来的，不用在此Encode
        if(this.hasRaw()){
            return enc.prependByteArray(this.getRaw(), BigInt(this.raw.byteLength))
        }

        let length = 0
        // try{
            // 编码 TLV-VALUE
            if(this.hasValue()){
                let tmpLen = 0
                try{
                    tmpLen =enc.prependByteArray(this.value, BigInt(this.value.byteLength))
                }catch (e){
                   throw e
                }
                // DONE 错误处理（TLV）
                length += tmpLen
            }else{
                // 如果 value 为空，则看 elements 中有没有子TLV Block，有则编码后插入
                if(this.elements != undefined){
                    for (let i = 0; i < this.elements.length; i++) {
                        let tmpLen = 0
                        try{
                            tmpLen = this.elements[i].encode(enc)
                        }catch (e){
                            throw e
                        }
                        // DONE 错误处理
                        length += tmpLen
                    }
                }
            }
            // 编码 TLV-LENGTH
            let tmpLen = enc.prependVarNumber((length))
            length += tmpLen

            // 编码 TLV-TYPE
            tmpLen = enc.prependVarNumber(this.tlvType)
            length += tmpLen

            return length
        // }catch(e){
        //     throw e
        // }
    }
}


class BlockError extends Error{
    constructor(m){
        super(m)
        this.name = 'BlockError'
    }

    error(){
        console.log(`{this.name}: {this.message}`)
    }
}


module.exports = {
    "Block": Block,
    "createBlockByTypeLengthBuffer": createBlockByTypeLengthBuffer,
    "createBlockByBuffer": createBlockByBuffer
}
